package com.xnx.bincker.leader.model;

import com.sun.javadoc.*;
import com.xnx.bincker.leader.BasicTest;
import com.xnx.bincker.leader.JavaDocUtils;
import com.xnx.bincker.leader.utils.ArraysUtils;
import com.xnx.bincker.leader.utils.StringUtils;
import lombok.Data;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.*;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

@RunWith(SpringRunner.class)
@SpringBootTest
@Ignore
public abstract class BasicModel<T> {
//    文档输出目录
    private static String OUT_DIRECTORY = "models/";

    @Rule
    private TestName testName;

    @Autowired
    private JavaDocUtils javaDocUtils;

    /**
     * 生成文档
     */
    void generateModelDoc(
            String title,
            Class<T> clazz,
            Collection<Field> ignoreFields,
            boolean jsonIgnore,
            boolean parentsPropertyIgnore,
            Class<?>... validationGroups
    ) {
        ClassDoc classDoc = javaDocUtils.getRootDoc().classNamed(clazz.getName());
        Assert.assertNotNull("找不到对象: className="+clazz.getName(), classDoc);

//        属性列表
        List<FieldInfo> fieldInfoList = getFields(
                classDoc,
                ignoreFields==null?Collections.emptyList():ignoreFields.stream().map(Field::getName).collect(Collectors.toList()),
                jsonIgnore,
                parentsPropertyIgnore
        );
//        排序
        fieldInfoList.sort(Comparator.comparing(a -> a.fileName));

//        输出文件
        File snippetFile = new File(BasicTest.OUT_DIRECTORY + OUT_DIRECTORY + clazz.getSimpleName() + ".adoc");
        assert snippetFile.getParentFile().exists() || snippetFile.getParentFile().mkdirs();
        try(BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(snippetFile)))) {
//            写出锚点
            writer.write("[[");
            writer.write(getClass().getSimpleName() + "." + testName.getMethodName());
            writer.write("]]\n");

//            写出标题
            writer.write("=== " + title);
            writer.write("\n");

//            说明
            writer.write(classDoc.commentText());
            writer.write("\n");

//            属性表
            writer.write("[options=\"header\"]\n");
            writer.write("|===\n");
            writer.write("|名称 |类型 |说明\n");
            for (FieldInfo field : fieldInfoList) {
                writer.write("|");
                writer.write(field.getFileName());
                writer.write("\n|");
                Type type = field.type;
                writer.write(field.getType().typeName());
                if(type instanceof ParameterizedType){
//                    BasicModel model = applicationContext.getBean()
                }
                String explain = (StringUtils.hasText(field.commentText)?field.commentText + "\n\n":"") + getFieldValidationExplain(field.annotationDescSet, validationGroups);
                if(StringUtils.hasText(explain)) {
                    writer.write("\na|");
                    writer.write(explain);
                    writer.write("\n");
                }else{
                    writer.write("\n|\n\n");
                }
            }
            writer.write("|===\n");
            writer.flush();
        }catch (IOException e){
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取属性列表
     * @param classDoc 获取对象
     * @param ignorePropertiesName 过滤属性表
     * @param jsonIgnore 是否过滤json过滤的属性（@JsonIgnore和@JsonPropertiesIgnore）
     * @param parentsPropertiesIgnore 是否过滤父类属性
     */
    public static List<FieldInfo> getFields(
            ClassDoc classDoc,
            Collection<String> ignorePropertiesName,
            boolean jsonIgnore,
            boolean parentsPropertiesIgnore
    ){
        List<FieldInfo> result = new ArrayList<>();
        Set<String> myIgnorePropertyNames = new HashSet<>(ignorePropertiesName);

//        如果忽略json忽略字段，取出类注解的过滤属性名称
        if(jsonIgnore){
            for (AnnotationDesc annotation : classDoc.annotations()) {
                if(annotation.annotationType().qualifiedName().equals("com.fasterxml.jackson.annotation.JsonIgnoreProperties")){
                    for (AnnotationDesc.ElementValuePair elementValuePair : annotation.elementValues()) {
                        if(elementValuePair.element().name().equals("value")){
                            myIgnorePropertyNames.addAll(Arrays.stream((AnnotationValue[]) elementValuePair.value().value()).map(v->(String)v.value()).collect(Collectors.toList()));
                            break;
                        }
                    }
                    break;
                }
            }
        }
//        获取属性
        for (Map.Entry<String, FieldInfo> fieldInfoEntry : getAllFields(classDoc, parentsPropertiesIgnore).entrySet()) {
            String fieldName = fieldInfoEntry.getKey();
            FieldInfo fieldInfo = fieldInfoEntry.getValue();
            if(myIgnorePropertyNames.contains(fieldName)) continue;
            if(jsonIgnore && fieldInfo.isJsonIgnore()) continue;
            result.add(fieldInfo);
        }
        return result;
    }

    /**
     * 取出类的所有字段信息
     * @param classDoc 类对象
     * @param ignoreParent 是否忽略父类
     */
    public static Map<String, FieldInfo> getAllFields(ClassDoc classDoc, boolean ignoreParent){
        if(classDoc == null || classDoc.qualifiedName().equals("java.lang.Object")) return Collections.emptyMap();
//        如果类标记有@Data则所有private方法将视为已有get，set方法
        boolean isDataClass = Arrays.stream(classDoc.annotations()).anyMatch(annotationDesc -> annotationDesc.annotationType().qualifiedName().equals("lombok.Data"));
        Map<String, FieldInfo> result = new HashMap<>();
        for (MethodDoc methodDoc : classDoc.methods()) {
            if(!methodDoc.isPublic() || methodDoc.isStatic()) continue;
            String methodName = methodDoc.name();
            String fieldName;
            String commentText = methodDoc.commentText();
            AnnotationDesc[] annotationDescs = methodDoc.annotations();
            Type type;
            if(methodName.startsWith("get")){
                fieldName = StringUtils.firstCharLowerCase(methodName.substring(3));
                type = methodDoc.returnType();
            }else if(methodName.startsWith("is")){
                fieldName = StringUtils.firstCharLowerCase(methodName.substring(2));
                type = methodDoc.returnType();
            }else continue;
            result.computeIfAbsent(fieldName ,key->{
                FieldInfo fieldInfo = new FieldInfo();
                fieldInfo.setFileName(key);
                fieldInfo.setCommentText(commentText);
                fieldInfo.setAnnotationDescSet(new HashSet<>(Arrays.asList(annotationDescs)));
                fieldInfo.setType(type);
                return fieldInfo;
            });
        }
        for (FieldDoc field : classDoc.fields()) {
            if((!isDataClass && !field.isPublic()) || field.isStatic()) continue;
            String fieldName = field.name();
            FieldInfo fieldInfo = result.computeIfAbsent(fieldName, key->new FieldInfo());
            fieldInfo.setFileName(fieldName);
            fieldInfo.setCommentText(field.commentText());
            fieldInfo.setType(field.type());
            Set<AnnotationDesc> annotationDescSet = fieldInfo.getAnnotationDescSet();
            if(annotationDescSet == null) {
                annotationDescSet = new HashSet<>();
                fieldInfo.setAnnotationDescSet(annotationDescSet);
            }
            annotationDescSet.addAll(Arrays.asList(field.annotations()));
        }
        if(!ignoreParent) {
            Map<String, FieldInfo> parentProperties = getAllFields(classDoc.superclass(), false);
            parentProperties.forEach((key, fieldInfo)->{
                if(!result.containsKey(key)) result.put(key, fieldInfo);
            });
        }
        return result;
    }

    /**
     * 属性信息
     */
    @Data
    public static class FieldInfo{
        private String fileName;
        private Type type;
        private Set<AnnotationDesc> annotationDescSet;
        private String commentText;

        /**
         * 判断是否JSON过滤
         */
        public boolean isJsonIgnore(){
            for (AnnotationDesc annotation : annotationDescSet) {
                if(annotation.annotationType().qualifiedName().equals("com.fasterxml.jackson.annotation.JsonIgnore")){
                    AnnotationDesc.ElementValuePair[] annotationValues = annotation.elementValues();
                    return annotationValues.length < 1 || (boolean) annotationValues[0].value().value();
                }
            }
            return false;
        }
    }


    /**
     * 取属性说明文档
     * @param annotationDescSet 注解列表
     * @param groups 属性值限制的分组
     */
    public static String getFieldValidationExplain(Collection<AnnotationDesc> annotationDescSet, @Nullable Class<?>... groups) {
        StringBuilder explain = new StringBuilder();
//            规则限制
        for (AnnotationDesc annotation : annotationDescSet) {
            Map<String, Object> valueMap = new HashMap<>();
            for (AnnotationDesc.ElementValuePair elementValuePair : annotation.elementValues()) {
                valueMap.put(elementValuePair.element().name(), elementValuePair.value().value());
            }
            AnnotationValue[] annotationGroups = (AnnotationValue[]) valueMap.get("groups");
            if((annotationGroups != null && annotationGroups.length > 0) && (groups != null && groups.length>0)){
//                    判断是否有验证分组
                if(!ArraysUtils.hasIntersection(groups, annotationGroups, (a,b)->a.getName().equals(((ProgramElementDoc)b.value()).qualifiedName()))) continue;
            }
            String qualifiedName = annotation.annotationType().qualifiedName();
            switch (qualifiedName){
//                    hibernate的验证注解
                case "org.hibernate.validator.constraints.CodePointLength":
                    explain.append("* ")
                            .append("文本长度(Unicode字符算一个字符)需要大于")
                            .append(valueMap.get("min"))
                            .append("且小于")
                            .append(valueMap.get("max"))
                            .append("\n");
                    break;
                case "org.hibernate.validator.constraints.CreditCardNumber":
                    explain.append("* 信用卡卡号\n");
                    break;
                case "org.hibernate.validator.constraints.Currency":
                    explain.append("* 货币\n");
                    break;
                case "org.hibernate.validator.constraints.EAN":
                    explain.append("* ").append(valueMap.get("type")).append("条码\n");
                    break;
                case "org.hibernate.validator.constraints.ISBN":
                    explain.append("* ").append(valueMap.get("type")).append("国际标准书号\n");
                    break;
                case "org.hibernate.validator.constraints.Length":
                    explain.append("* ")
                            .append("文本长度需要大于")
                            .append(valueMap.get("min"))
                            .append("且小于")
                            .append(valueMap.get("max"))
                            .append("\n");
                    break;
                case "org.hibernate.validator.constraints.LuhnCheck":
                    explain.append("* 需要通过Luhn输入错误校验\n");
                    break;
                case "org.hibernate.validator.constraints.Mod10Check":
                    explain.append("* 需要通过模10校验\n");
                    break;
                case "org.hibernate.validator.constraints.Mod11Check":
                    explain.append("* 需要通过模11校验\n");
                    break;
                case "org.hibernate.validator.constraints.Range":
                    explain.append("* 数值需要在").append(valueMap.get("min")).append("-").append(valueMap.get("max")).append("区间范围内\n");
                    break;
                case "org.hibernate.validator.constraints.SafeHtml":
                    explain.append("* 安全的HTML内容\n");
                    break;
                case "org.hibernate.validator.constraints.UniqueElements":
                    explain.append("* 集合的每一项都是唯一的\n");
                    break;
                case "org.hibernate.validator.constraints.URL":
                    explain.append("* 是一个URL\n");
                    break;

//                    javax的验证注解
                case "javax.validation.constraints.AssertFalse":
                    explain.append("* 必须为False\n");
                    break;
                case "javax.validation.constraints.AssertTrue":
                    explain.append("* 必须为True\n");
                    break;
                case "javax.validation.constraints.DecimalMax":
                    explain.append("* 小于");
                    if((boolean) valueMap.get("inclusive")) explain.append("或等于");
                    explain.append(valueMap.get("value")).append("\n");
                    break;
                case "javax.validation.constraints.DecimalMin":
                    explain.append("* 大于");
                    if((boolean) valueMap.get("inclusive")) explain.append("或等于");
                    explain.append(valueMap.get("value")).append("\n");
                    break;
                case "javax.validation.constraints.Digits":
                    explain.append("* 小数点前最多").append(valueMap.get("integer")).append("位数字，")
                            .append("* 小数点后最多").append(valueMap.get("fraction")).append("位数字\n");
                    break;
                case "javax.validation.constraints.Email":
                    explain.append("* 是一个邮箱\n");
                    break;
                case "javax.validation.constraints.Future":
                    explain.append("* 大于现在时间\n");
                    break;
                case "javax.validation.constraints.FutureOrPresent":
                    explain.append("* 大于或等于现在的时间\n");
                    break;
                case "javax.validation.constraints.Max":
                    explain.append("* 小于或等于").append(valueMap.get("value")).append("\n");
                    break;
                case "javax.validation.constraints.Min":
                    explain.append("* 大于或等于").append(valueMap.get("value")).append("\n");
                    break;
                case "javax.validation.constraints.Negative":
                    explain.append("* 小于零的数值\n");
                    break;
                case "javax.validation.constraints.NegativeOrZero":
                    explain.append("* 小于或等于零的数值\n");
                    break;
                case "javax.validation.constraints.NotBlank":
                    explain.append("* 文本不能为空\n");
                    break;
                case "javax.validation.constraints.NotEmpty":
                    explain.append("* 不能为空\n");
                    break;
                case "javax.validation.constraints.NotNull":
                    explain.append("* 不能为Null\n");
                    break;
                case "javax.validation.constraints.Null":
                    explain.append("* 必须为Null\n");
                    break;
                case "javax.validation.constraints.Past":
                    explain.append("* 小于现在的时间\n");
                    break;
                case "javax.validation.constraints.PastOrPresent":
                    explain.append("* 小于或等于现在的时间\n");
                    break;
                case "javax.validation.constraints.Pattern":
                    explain.append("* 需要满足正则表达式:\n[source,regexp]\n----\n")
                            .append(((String) valueMap.get("regexp")).replaceAll("\\|", "\\\\|"))
                            .append("\n----\n");
                    break;
                case "javax.validation.constraints.Positive":
                    explain.append("* 大于零的数值\n");
                    break;
                case "javax.validation.constraints.PositiveOrZero":
                    explain.append("* 大于或等于零的数值\n");
                    break;
                case "javax.validation.constraints.Size":
                    explain.append("* 长度在区间").append(valueMap.get("min")).append("-").append(valueMap.get("max")).append("内\n");
                    break;
            }
        }
        return explain.toString();
    }
}
